/*
 * Copyright (c) 2006-2022, Ulandlink Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2022-12-12     xpw          first version
 */

#include <rtconfig.h>

#ifdef USING_PPP_DEVICE

#include <stdio.h>
#include <stdint.h>
#include <string.h>

#include <sys/select.h>

#ifdef RT_USING_SAL
#include <sys/socket.h>
#else 
#include <lwip/sockets.h>
#endif

#define DBG_TAG     "ppp.utils.w15"
#define DBG_LVL     DBG_INFO

#include <rtdbg.h>
#include "ppp_utils_w15.h"
#include "ppp_device_w15.h"
#include <ipc/workqueue.h>

#define DEBUG_CMD_ENABLE 0

#define W15_AT_LOCAL_ADDR               "0.0.0.0"
#define W15_AT_LOCAL_PORT               5936

#define W15_AT_SERVER_ADDR              "172.30.0.251"
#define W15_AT_SERVER_PORT              23

#define W15_AT_RECV_THRD_PRIORITY       18
#define W15_AT_RECV_THRD_STACK_SIZE     2048
#define W15_AT_RECV_MAXLEN              64

#define W15_RESP_OK_EVENT               (1<<0)
#define W15_RESP_ERROR_EVENT            (2<<0)
#define W15_RESP_EVENT_SET              (W15_RESP_OK_EVENT | W15_RESP_ERROR_EVENT)                             
#define W15_RESP_EVENT_OPT_FLAG         (RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR)

struct w15_urc_func {
    char *urc;
    void (*callback)(char *data);
};

static int atp_socket = -1;
static char w15_ip[16] = {0};
static struct sockaddr_in server_addr;

static struct rt_work wifi_work;

static rt_bool_t wifi_link_flag = RT_FALSE; 

/* define w15 recv event */
static struct rt_event w15_resp_event;


/***************************************************
                w15 resp event 
***************************************************/
static void w15_resp_event_send(uint32_t event)
{
	rt_event_send(&w15_resp_event, event);
}

static void w15_resp_event_recv(uint32_t event_set, rt_int32_t timeout, uint32_t *recv_event){
    *recv_event = 0;
    rt_event_recv(&w15_resp_event, event_set, W15_RESP_EVENT_OPT_FLAG, timeout, recv_event);
}

static void w15_resp_event_clear(void)
{
    rt_uint32_t recv_event;
    rt_event_recv(&w15_resp_event, W15_RESP_EVENT_SET, W15_RESP_EVENT_OPT_FLAG, MSG_DONTWAIT, &recv_event);
}

/***************************************************
            send w15 msg on udp port 23
***************************************************/
static int w15_send_cmd(char *msg)
{
    int ret;
    char *data = RT_NULL;
    int length = strlen(msg) + 2;
    
    if (atp_socket < 0) {
        LOG_W("send %s to w15 error.", msg);
        return RT_ERROR;
    }
    
    data = (char *)rt_malloc(length);
    rt_memset(data, 0, length);
    rt_memcpy(data, msg, strlen(msg));
    rt_memcpy(data + strlen(msg), "\r\n", 2);
    
    LOG_D("send %.*s to w15.", length - 2, data);
    
    ret = sendto(atp_socket, data, length, 0, (struct sockaddr*)&server_addr, sizeof(server_addr));
    rt_free(data);
    
    return ret;
}

/***************************************************
                w15 work queque 
***************************************************/
static void w15_wifi_work(struct rt_work *work, void *work_data)
{
    while (1) {
        w15_send_cmd("AT+UNSTAT=0,101");
        rt_thread_mdelay(2000);
        if (strlen(w15_ip) > 0 && !rt_strstr(w15_ip, "0.0.0.0")) {
            return;
        }
    }
}

/***************************************************
                w15 urc callback 
***************************************************/
static void urc_wifi_func(char *data)
{
    LOG_D("get urc: %.*s", strlen(data + 2) - 2, data + 2);
    
    if (rt_strstr(data + 2, "+UUWLE")) {
        LOG_I("wifi connected.");
        rt_work_submit(&wifi_work, 50);
        wifi_link_flag = RT_TRUE;
    } else if (rt_strstr(data + 2, "+UUWLD")) {
        if (strlen(w15_ip) > 0) {
            LOG_I("wifi disconnected.");
            rt_memset(w15_ip, 0, sizeof(w15_ip));
        }
        wifi_link_flag = RT_FALSE;
    }
}

static void urc_hotspot_func(char *data)
{
    LOG_D("get urc: %.*s", strlen(data + 2) - 2, data + 2);
    
    if (rt_strstr(data + 2, "+UUWAPU")) {
        LOG_I("hotspot is on.");
    }
}

static void urc_get_ip_func(char *data)
{        
    LOG_D("get urc: %.*s", strlen(data + 2) - 2, data + 2);
    
    rt_memset(w15_ip, 0, sizeof(w15_ip));
    
    sscanf(data + 2, "+UNSTAT:0,101,%[^\r]", w15_ip);
    
    if (rt_strstr(w15_ip, "0.0.0.0")) {
        return;
    }
    
    LOG_I("w15 get ipv4 %s", w15_ip);
}

const static struct w15_urc_func urc_table[] = {
    {"+UUWLE:", urc_wifi_func},
    {"+UUWLD:", urc_wifi_func},
    {"+UUWAPU:", urc_hotspot_func},
    {"+UNSTAT:", urc_get_ip_func},
};

static void w15_check_urc_msg(char *data)
{
    for (int i = 0; i < sizeof(urc_table)/sizeof(urc_table[0]); i ++) {
        if (!rt_strncmp(data + 2, urc_table[i].urc, strlen(urc_table[i].urc))) {
            urc_table[i].callback(data);
            return;
        }
    }
}

/***************************************************
            recv w15 msg on udp port 23
***************************************************/
static void w15_recv_at_msg_thread(void *parameter)
{
    int ret;
    char data[W15_AT_RECV_MAXLEN] = {0};
    
    while (1) {
        ret = recv(atp_socket, data, sizeof(data), 0);    
        if (ret > 0) {
            //rt_kprintf("%s", data);
            if (rt_strstr(data, "OK\r\n")) {
                w15_resp_event_send(W15_RESP_OK_EVENT);
            } else if (rt_strstr(data, "ERROR\r\n")) {
                w15_resp_event_send(W15_RESP_ERROR_EVENT);
            } else {
                w15_check_urc_msg(data);
            }
            memset(data, 0, ret);
        }               
        rt_thread_mdelay(20);
    }
    
    return;
}

char *get_pp_wifi_ip(void)
{
    static char no[16] = "0.0.0.0";
    
    if (strlen(w15_ip) == 0) {
        return no;
    }
    return w15_ip;
}

rt_bool_t get_wifi_status(void)
{
    return wifi_link_flag;
}

int w15_recv_at_msg_init(void)
{
    rt_thread_t tid;
    int result = -RT_ERROR;
    struct netdev *device = RT_NULL;
    struct sockaddr_in local_addr = {0};
    
    while (1) {
        device = netdev_get_by_name("pp");
        if (device && netdev_is_link_up(device)) {
            break;
        }
        rt_thread_mdelay(2000);
    }
    
    atp_socket = socket(AF_INET, SOCK_DGRAM, 0);
    
    if (atp_socket < 0) {
        LOG_E("ppp device w15 create socket failed.");
        goto __exit;
    }
    
    local_addr.sin_family = AF_INET;
    local_addr.sin_port = htons(W15_AT_LOCAL_PORT);
    local_addr.sin_addr.s_addr = inet_addr(W15_AT_LOCAL_ADDR);
    
    if (bind(atp_socket, (struct sockaddr*)&local_addr, sizeof(local_addr)) < 0) {
        LOG_W("ppp device w15 bind socket failed.");
    }        
    
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(W15_AT_SERVER_PORT);
    server_addr.sin_addr.s_addr = inet_addr(W15_AT_SERVER_ADDR);
    rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));
    
//    int size = 1024;
//    setsockopt(atp_socket, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
    
    if (rt_event_init(&w15_resp_event, "w15_recv_event", RT_IPC_FLAG_FIFO) < 0) {
        LOG_E("w15 recv event init failed.");
        goto __exit;
    }
    
    rt_work_init(&wifi_work, w15_wifi_work, RT_NULL);
    
    tid = rt_thread_create( "w15_at_recv", w15_recv_at_msg_thread, RT_NULL, 
            W15_AT_RECV_THRD_STACK_SIZE, W15_AT_RECV_THRD_PRIORITY, 5);

    if (tid != RT_NULL) {
        rt_thread_startup(tid);
        LOG_D("w15 recv at msg thread init success.");
        sendto(atp_socket, "AT\r\n", 4, 0, (struct sockaddr*)&server_addr, sizeof(server_addr));
        rt_thread_mdelay(100);
        
        w15_wifi_enable(RT_TRUE);
        w15_hotspot_enable(RT_TRUE);
        
        result = RT_EOK;
    } else {
        LOG_E("w15 recv at msg thread init failed.");
    }
    
__exit:
    
    return result;
}
INIT_APP_EXPORT(w15_recv_at_msg_init);

int w15_exec_at_cmd(int socket, const char *cmd_expr, ...)
{
    int ret;
    va_list args;
    char cmd[64] = {0};
    char buffer[128] = {0};
    rt_uint32_t recv_event;
    int result = -RT_ERROR;
    
    if (atp_socket < 0) {
        goto __exit;
    }
    
    va_start(args, cmd_expr);
    vsnprintf(cmd, sizeof(cmd) - 3, cmd_expr, args);
    va_end(args);
    strcat(cmd, "\r\n");
    
    w15_resp_event_clear();
    
    ret = sendto(atp_socket, cmd, strlen(cmd), 0, (struct sockaddr*)&server_addr, sizeof(server_addr));    
    if (ret > 0) {
        LOG_D("at cmd(%.*s) sent.", (ret < strlen(cmd)) ? ret : (strlen(cmd) - 2), cmd);
    } else {
        LOG_E("at cmd(%.*s) sent failed.", strlen(cmd) - 2, cmd);
        goto __exit;
    }
    
    w15_resp_event_recv(W15_RESP_EVENT_SET, RT_TICK_PER_SECOND * 3, &recv_event);
    if (recv_event & W15_RESP_OK_EVENT) {
        result = RT_EOK;
        LOG_D("exec at cmd(%.*s) success.", strlen(cmd) - 2, cmd);
        goto __exit;
    }
    if (recv_event & W15_RESP_ERROR_EVENT) {
        LOG_E("exec at cmd(%.*s) failed.", strlen(cmd) - 2, cmd);
        goto __exit;
    }
    
    LOG_W("get at cmd(%.*s) result timeout.", strlen(cmd) - 2, cmd);
    result = -RT_ETIMEOUT;

__exit:
    return result;    
}
#include <unistd.h>
/* these functions are for test w15 mudoule */
#if DEBUG_CMD_ENABLE
/* execute device w15 AT command by specified UDP port */
static int w15(int argc, char **argv)
{
    if (argc < 2) {
        rt_kprintf("Usage:\n");
        rt_kprintf("w15 [AT command]            - execute w15 AT command\n");
        return -RT_ERROR;
    }
    
    return w15_exec_at_cmd(atp_socket, argv[1]);
}
MSH_CMD_EXPORT(w15, execute device w15 AT command);
#endif
#endif
